#include <assert.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/time.h>
#include <time.h>

#include <xmlsec/xmltree.h>
#include <xmlsec/xmlenc.h>

#include "common.h"
#include "config.h"
#include "paths.h"

struct compliance {
  struct signedDocument *compliance, *proposition, *solicitation;
  xmlNodePtr assents, conditions, access, authority, remuneration;
  const char *ip, *chain, *dst;
  unsigned long quota;
  u_int32_t mark;
};

u_int32_t chooseMark(u_int32_t base) {
  unsigned char map[128];
  DIR *d;
  struct timeval tv;
  struct dirent *de;
  u_int32_t ret;
  int sanity;

  memset(map, 0, 128);
  d = opendir(LOCALSTATEDIR);
  assert(d);
  while((de = readdir(d))) {
    u_int32_t e;
    char *ptr;

    fprintf(stderr, "name %s\n", de->d_name);
    if(!strcmp(de->d_name, ".") ||
       !strcmp(de->d_name, ".."))
      continue;
    e = strtoul(de->d_name, &ptr, 16);
    if(strcmp(ptr, ".xml"))
      fprintf(stderr, "Odd2 filename %s\n", de->d_name);
    else
      fprintf(stderr, "filename parsed to %x\n", e);
    if(e < base || e >= base + 0x1000)
      continue;
    e -= base;
    map[e >> 3] |= (1 << (e & 7));
  }
  fprintf(stderr, "files done\n");
  closedir(d);
  gettimeofday(&tv, NULL);
  srand(tv.tv_sec ^ tv.tv_usec ^ getpid());
  sanity = 0;
  do {
    ret = rand() & 0xfff;
    if(!ret)
      continue; // 0 is a special case in the firewalling setup
    fprintf(stderr, "trying %x\n", ret);
    sanity++;
  } while(sanity < 1000 && (map[ret >> 3] & 1 << (ret & 0x7)));
  fprintf(stderr, "returning %x after %d\n", ret, sanity);
  return ret + base;
}

xmlNodePtr findOrDie(xmlNodePtr root, const unsigned char * const namespace,
		     const unsigned char * const tag) {
  xmlNodePtr ret;

  ret = xmlSecFindChild(root, tag, namespace);
  if(ret)
    return ret;
  fprintf(stderr, "Failed to find %s::%s under %s::%s\n", namespace, tag,
	  xmlSecGetNodeNsHref(root), xmlSecNodeGetName(root));
  ret = root->xmlChildrenNode;
  if(ret)
    do
      fprintf(stderr, "\tTried %s::%s\n",
	      xmlSecGetNodeNsHref(ret), xmlSecNodeGetName(ret));
    while((ret = ret->next));
    
  assert(0);
  return NULL;
}

struct action {
  int (*act)(struct action *);
  void (*undo)(struct action *);
  union {
    void *vp;
    xmlNodePtr node;
    xmlDocPtr doc;
    xmlSecEncCtxPtr ec;
    int i;
    char *str;
  } a, b, c;
  struct compliance *compliance;
};

struct action *makeAction(struct compliance *c,
			  int (*act)(struct action *),
			  void (*undo)(struct action *)) {
  struct action *a;
  a = malloc(sizeof(*a));
  memset(a, 0, sizeof(*a));
  a->compliance = c;
  a->act = act;
  a->undo = undo;
  return a;
}

struct remuneration *makeRemuneration(xmlNodePtr p) {
  struct remuneration *ret;
  xmlNodePtr cur;
  xmlChar *txt;
  char *ptr;

  ret = malloc(sizeof(*ret));
  assert(ret);
  memset(ret, 0, sizeof(*ret));
  cur = findOrDie(p, ns_mikolaj, "ratingQuantum");
  txt = xmlNodeListGetString(cur->doc, cur->xmlChildrenNode, 1);
  ret->quantum = strtoul(txt, &ptr, 10);
  assert(ret->quantum);
  assert(ptr);
  assert(!*ptr);
  xmlFree(txt);
  ret->amount = cur = findOrDie(p, ns_payment, "amount");
  txt = xmlNodeListGetString(cur->doc, cur->xmlChildrenNode, 1);
  fprintf(stderr, "rate %s\n", txt);
  ret->rate = strtod(txt, &ptr);
  ret->currency = xmlGetNoNsProp(cur, "currency");
  assert(ret->rate);
  assert(ret->currency);
  assert(ptr);
  assert(!*ptr);
  xmlFree(txt);
  fprintf(stderr, "made remuneration %g/%ld\n", ret->rate, ret->quantum);
  return ret;
}

int doDebitAuthority(xmlNodePtr paymentAuthority, struct remuneration *rem) {
  return sendTxn(makeSoapReq(xmlDocGetRootElement(makeDrawdown(paymentAuthority, rem))));
}

int debitAuthority(struct action *act) {
  struct remuneration *rem = act->a.vp;
  xmlDocPtr pa = act->b.doc;
  return sendTxn(makeSoapReq(xmlDocGetRootElement(makeDrawdown(xmlDocGetRootElement(pa), rem))));
}

void refundAuthority(struct action *act) {
  assert(!"can't undo drawdown!");
}

int configureIPSec(struct action *act) {
  char cmd[400];
  int spi = act->a.i;
  char *key = act->b.str;

  fprintf(stderr, "Configuring ipsec!\n");
  snprintf(cmd, 399,
	   "ipsec spi --af inet --edst %s --spi 0x%x --proto ah --src %s --ah hmac-sha1-96 --authkey %s",
	   act->compliance->dst, spi, act->compliance->ip, key);
  fprintf(stderr, "ipsec cmd %s\n", cmd);
  snprintf(cmd, 399,
	   "ipsec eroute --add --eraf inet --src %s --dst %s --said ah0x%x@%s",
	   act->compliance->ip, act->compliance->dst, spi, act->compliance->dst);
  fprintf(stderr, "ipsec cmd %s\n", cmd);
  act->compliance->chain = "ipsec";
  return 0;
}

int doConfigureNetwork(unsigned int mark, unsigned long quota) {
  char cmd[200];
  int ret;

#if 0
  if(quota)
    snprintf(cmd, 199,
	     PATH_IPTABLES " -A topup -d %s -m quota --quota %ld -j ACCEPT",
	     ip, quota);
  else
    snprintf(cmd, 199, PATH_IPTABLES " -A topup -d %s -j ACCEPT",
	     ip);
  if((ret = system(cmd))) {
    fprintf(stderr, "Command failed with %d (%s)\n", ret, cmd);
    return 1;
  }
  return 0;
#endif
  snprintf(cmd, 199,
	   PATH_IPTABLES " -t mangle -A charon-quota -m mark --mark 0x%x -m quota --quota %ld -j MARK --set-mark 0x3001",
	   mark, quota);
  if((ret = system(cmd))) {
    fprintf(stderr, "Command failed with %d (%s)\n", ret, cmd);
    return 1;
  }
  return 0;
}

int configureNetwork(struct action *act) {
  char cmd[200];
  int ret;

  if(act->compliance->quota)
    act->compliance->mark = chooseMark(0x2000);
  else
    act->compliance->mark = 0x3000;
  fprintf(stderr, "Mark %x\n", act->compliance->mark);
  snprintf(cmd, 199, PATH_IPTABLES " -t mangle -A charon-outbound -s %s -j MARK --set-mark 0x%x",
	   act->compliance->ip, act->compliance->mark);
  if((ret = system(cmd))) {
    fprintf(stderr, "Command failed with %d (%s)\n", ret, cmd);
    return 1;
  }
  snprintf(cmd, 199, PATH_IPTABLES " -t mangle -A charon-inbound -d %s -j MARK --set-mark 0x%x",
	   act->compliance->ip, act->compliance->mark);
  if((ret = system(cmd))) {
    fprintf(stderr, "Command failed with %d (%s)\n", ret, cmd);
    return 1;
  }
  if(act->compliance->quota) {
    snprintf(cmd, 199,
	     PATH_IPTABLES " -t mangle -A charon-quota -m mark --mark 0x%x -m quota --quota %ld -j MARK --set-mark 0x3001",
	     act->compliance->mark, act->compliance->quota);
    if((ret = system(cmd))) {
      fprintf(stderr, "Command failed with %d (%s)\n", ret, cmd);
      return 1;
    }
  }  
  return 0;
}

void removeNetwork(struct action *act) { 
  fprintf(stderr, "Removing network!\n");
}

void removeIPSec(struct action *act) { 
  fprintf(stderr, "Removing ipsec!\n");
}

void record(struct compliance *c) {
  xmlNodePtr root;
  FILE *f;
  char filename[80];
  int fd, retry = 50;

  root = xuNewDoc(ns_config, "cfg", "user");
  xmlAddChild(root, xmlNewDocNode(root->doc, root->ns, "ip", c->ip));
  xmlAddChild(root, xmlDocCopyNode(c->remuneration, root->doc, 1));
  xmlAddChild(root, xmlDocCopyNode(c->authority, root->doc, 1));
  do {
    snprintf(filename, 79, LOCALSTATEDIR "/%08x.xml", c->mark);
  } while((fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0600)) < 0 && --retry);
  if(!retry)
    return;
  f = fdopen(fd, "w");
  if(f) {
    xmlDocDump(f, root->doc);
    fclose(f);
  }
}

struct traversal {
  int fail;
  GSList *unwind;
};

static int execcount;

gint execute(gpointer key, gpointer value, gpointer data) {
  struct traversal *t = data;
  struct action *act = value;
  int ret;

  execcount++;
  fprintf(stderr, "*** Try %d\n", execcount);
  if((ret = act->act(act))) {
    fprintf(stderr, "*** Fail %d! %d\n", execcount, ret);
    t->fail++;
    return TRUE;
  }
  fprintf(stderr, "*** OK %d\n", execcount);
  t->unwind = g_slist_append(t->unwind, act);
  return FALSE;
}

void unwind(gpointer value, gpointer data) {
  struct action *act = value;
  act->undo(act);
}

int processCompliance(xmlNodePtr root, const char *ip) {
  struct compliance *c;
  xmlNodePtr cur;
  GTree *actions;
  struct traversal t;
  struct action *a;
  xmlDocPtr tmp;
  
  c = calloc(1, sizeof(*c));

  c->ip = ip;
  c->chain = "users";
  
  fprintf(stderr, "validating compliance\n");
  c->compliance = validateDocument("file://" DATADIR "/charon.xsd", root);
  assert(c->compliance);
  cur = c->compliance->signedRoot->xmlChildrenNode;
  assert(cur);
  while(xmlStrcmp(xmlSecGetNodeNsHref(cur), "urn:frog") ||
       xmlStrcmp(xmlSecNodeGetName(cur), "proposition"))
    cur = cur->next;
  assert(cur);
  
  tmp = xmlNewDoc("1.0");
  cur = xmlDocCopyNode(cur, tmp, 1);
  xmlDocSetRootElement(tmp, cur);
  fprintf(stderr, "validating proposition\n");
  c->proposition = validateDocument("file://" DATADIR "/charon.xsd", cur);
  assert(c->proposition);
  cur = c->proposition->signedRoot->xmlChildrenNode;
  assert(cur);
  while(xmlStrcmp(xmlSecGetNodeNsHref(cur), "urn:frog") ||
       xmlStrcmp(xmlSecNodeGetName(cur), "solicitation"))
    cur = cur->next;
  assert(cur);
    
  tmp = xmlNewDoc("1.0");
  cur = xmlDocCopyNode(cur, tmp, 1);
  xmlDocSetRootElement(tmp, cur);
  fprintf(stderr, "validating solicitation\n");
  c->solicitation = validateDocument("file://" DATADIR "/charon.xsd", cur);
  assert(c->solicitation);

  /* now we have all three documents */

  fprintf(stderr, "all docs found\n");
  c->assents = findOrDie(c->compliance->signedRoot, ns_mikolaj, "assent");
  c->conditions = findOrDie(c->proposition->signedRoot, ns_mikolaj, "condition");
  c->access = findOrDie(c->solicitation->signedRoot, ns_mikolaj, "Address");

  c->dst = xmlNodeListGetString(c->access->doc,
				c->access->xmlChildrenNode, 1);
  /* match conditions against assents */
  cur = c->conditions->xmlChildrenNode;
  assert(cur);			/* must have at minimum an ipsec
				   condition... right? */
  actions = g_tree_new((GCompareFunc)strcmp);
  a = makeAction(c, configureNetwork, removeNetwork);
  g_tree_insert(actions, "z", a);

  do {
    const char *node;
      
    assert(!xmlStrcmp(xmlSecGetNodeNsHref(cur), "urn:frog"));
    node = xmlSecNodeGetName(cur);
    if(!xmlStrcmp(node, "remuneration")) {
      xmlNodePtr pa;
      struct remuneration *r;

      pa = xmlSecFindChild(c->assents, "authority", "urn:dosh");
      assert(pa);
      /* validate payment authority */
      a = makeAction(c, debitAuthority, refundAuthority);
      r = makeRemuneration(cur);
      a->a.vp = r;
      c->quota = r->quantum;
      a->b.doc = xmlNewDoc("1.0");
      xmlDocSetRootElement(a->b.doc, xmlDocCopyNode(pa, a->b.doc, 1));
      g_tree_insert(actions, "p", a);
      fprintf(stderr, "Have payment authority!\n");
      c->authority = pa;
      c->remuneration = cur;
      c->chain = "topup";
      continue;
    }
    if(!xmlStrcmp(node, "ipsec")) {
      xmlNodePtr ik, enc, spinode;
      xmlSecEncCtxPtr ec;
      int i;
      xmlChar *txt;

      ik = xmlSecFindChild(c->assents, "ipsecKey", "urn:frog");
      assert(ik);
      /* bugger. extract shared secret, examine access, and queue
	 appropriate actions to execute list? */
      fprintf(stderr, "Noted ipsec condition\n");

      spinode = findOrDie(cur, ns_mikolaj, "spi");
      
      enc = xmlSecFindChild(ik, xmlSecNodeEncryptedKey, xmlSecEncNs);
      assert(enc);
      ec = xmlSecEncCtxCreate(NULL);
      assert(ec);
      ec->encKey = loadRootKey();
      ec->mode = xmlEncCtxModeEncryptedKey;
      assert(ec->encKey);
      assert(!xmlSecEncCtxDecrypt(ec, enc));
      assert(ec->result);

      a = makeAction(c, configureIPSec, removeIPSec);
      txt = xmlNodeListGetString(spinode->doc, spinode->xmlChildrenNode, 1);
      a->a.i = atoi(txt);
      xmlFree(txt);
      a->b.str = malloc(200);
      strcpy(a->b.str, "0x");
      for(i = 0; i < xmlSecBufferGetSize(ec->result); i++)
	snprintf(a->b.str + 2 + i * 2, 3, "%02x", xmlSecBufferGetData(ec->result)[i]);
      fprintf(stderr, "key %s \n", a->b.str);
      g_tree_insert(actions, "s", a);
      continue;
    }
    // XXX - must die on unknown conditions, but currently this picks
    // up whitespace. must fix.
    fprintf(stderr, "Unknown condition '%s'!\n", node);
  } while((cur = cur->next));
  memset(&t, 0, sizeof(t));
  g_tree_traverse(actions, execute, G_IN_ORDER, &t);
  fprintf(stderr, "Done traverse\n");
  if(t.fail) {
    fprintf(stderr, "Unwinding %d!\n", g_slist_length(t.unwind));
    g_slist_foreach(t.unwind, unwind, NULL);
    return 1;
  } else {
    record(c);
    // return success to user!
    return 0;
  }
}

